package com.yingwei.mining.control;

import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.yingwei.mining.model.businessobject.Excavator;
import com.yingwei.mining.model.businessobject.Node;
import com.yingwei.mining.model.businessobject.Route;
import com.yingwei.mining.model.businessobject.Truck;
import com.yingwei.mining.model.businessobject.UnloadArea;
import com.yingwei.mining.model.businessobject.WorkOrder;
import com.yingwei.mining.util.LocationUtil;

public class DataScheduler
{
    private static Logger logger = LoggerFactory.getLogger(DataScheduler.class);
    Data data = Data.getData();

    public WorkOrder createNextWorkOrder(Truck truck)
    {
	long now = System.currentTimeMillis();

	Node startNode = LocationUtil.findNearbyNode(truck.getX(),
		truck.getY(), data.getNodes());

	long bestTotalTimeForOneExcavator = Long.MAX_VALUE;
	long bestTruckArrivalTime = Long.MAX_VALUE;
	long bestLoadTime = Long.MAX_VALUE;
	Route bestLoadRoute = null;
	Route bestUnloadRoute = null;
	Excavator bestExcavator = null;
	UnloadArea bestUnloadArea = null;

	for (Excavator excavator : data.getExcavators())
	{
	    // find all available excavators
	    if (!excavator.getStatus().getStatusId().equalsIgnoreCase(
		    Constant.EXCAVATOR_STATUS_INACTIVE))
	    {
		Node excavatorNode = data.findNodeByDevice(excavator);
		if (excavatorNode != null)
		{
		    Route tmpLoadRoute = null;
		    long tmpTruckArrivalTime = Long.MAX_VALUE;
		    Route route = data.calculateRoute(startNode, data
			    .findNodeByDevice(excavator));
		    tmpTruckArrivalTime = distanceToTime(route.getValue(),
			    truck);
		    tmpLoadRoute = route;

		    // 1. find arrival time
		    tmpTruckArrivalTime += now;
		    // 2. find loading time
		    // for this combination of truck and excavator.
		    int numOfLoad = (int) Math.floor((double) (truck.getModel()
			    .getCapacity())
			    / excavator.getModel().getCapacity());
		    long timeForLoading = numOfLoad
			    * excavator.getModel().getTimePerLoad();
		    // 3. adjust arrival time.
		    // find the next available time slot from workOrders.
		    tmpTruckArrivalTime = this.availableTimeSlot(excavator,
			    tmpTruckArrivalTime, timeForLoading);
		    // 4. find the shortest unloading path.
		    int unloadPathValue = Integer.MAX_VALUE;
		    Route tmpUnloadRoute = null;
		    UnloadArea tmpUnloadArea = null;
		    for (UnloadArea ua : data.getUnloadAreas())
		    {
			if (!ua.getStatus().getStatusId().equalsIgnoreCase(
				Constant.UNLOAD_AREA_STATUS_INACTIVE))
			{
			    Node to = data.findNodeByDevice(ua);
			    if (to != null)
			    {
				Route rt = data.calculateRoute(excavatorNode,
					to);
				if (rt != null)
				{
				    if (ua.getMaterial().getMaterialId()
					    .equalsIgnoreCase(
						    excavator.getMaterial()
							    .getMaterialId()))
				    {
					// must match material.
					if (rt.getValue() < unloadPathValue)
					{
					    unloadPathValue = rt.getValue();
					    tmpUnloadRoute = rt;
					    tmpUnloadArea = ua;
					}
				    }
				}
			    }
			}
		    }
		    if (tmpUnloadRoute != null)
		    {
			// 5. time needed on travel to unload area.
			long tmpUnloadTravelTime = distanceToTime(
				tmpUnloadRoute.getValue(), truck);
			// 6. the best route for this excavator
			long time = tmpTruckArrivalTime + timeForLoading
				+ tmpUnloadTravelTime;
			if (bestTotalTimeForOneExcavator > time)
			{
			    bestTotalTimeForOneExcavator = time;
			    bestLoadRoute = tmpLoadRoute;
			    bestUnloadRoute = tmpUnloadRoute;
			    bestExcavator = excavator;
			    bestUnloadArea = tmpUnloadArea;
			    bestTruckArrivalTime = tmpTruckArrivalTime;
			    bestLoadTime = timeForLoading;
			}
		    }
		}
	    }
	}
	if (bestLoadRoute != null)
	{
	    WorkOrder wo = new WorkOrder(truck, bestExcavator, bestUnloadArea,
		    bestExcavator.getMaterial(), bestLoadRoute,
		    bestUnloadRoute, truck.getX(), truck.getY(), truck.getZ(),
		    Constant.WORK_ORDER_STATUS_SCHEDULED, new Date(
			    bestTruckArrivalTime), new Date(
			    bestTruckArrivalTime + bestLoadTime), null, null,
		    null, new Date(bestTotalTimeForOneExcavator),
		    new Date(bestTotalTimeForOneExcavator
			    + bestUnloadArea.getModel().getUnloadTime()), null,
		    null, null, new Date(), new Date());
	    return wo;
	}
	else
	// no work order can be generated.
	{
	    logger.debug("no work order can be generated for truck."
		    + truck.getId());
	    return null;
	}
    }

    /**
     * use the try and fail algorithm to find the next time slot.
     * 
     * @param excavator
     * @param truckArrivalTime
     * @param timeForLoading
     * @return
     */
    private long availableTimeSlot(Excavator excavator, long truckArrivalTime,
	    long timeForLoading)
    {
	long original = truckArrivalTime;
	for (WorkOrder workOrder : data.getWorkOrders())
	{
	    if (workOrder.getExcavator().getId() == excavator.getId())
	    {
		long time1 = workOrder.getLoadScheduleStartTime().getTime();
		long time2 = workOrder.getLoadScheduleEndTime().getTime();
		if (truckArrivalTime > time1 && truckArrivalTime < time2)
		{
		    truckArrivalTime = time2;
		    break;
		}
		if (truckArrivalTime + timeForLoading > time1
			&& truckArrivalTime + timeForLoading < time2)
		{
		    truckArrivalTime = time2;
		    break;
		}
	    }
	}
	if (truckArrivalTime != original)
	{
	    truckArrivalTime = availableTimeSlot(excavator, truckArrivalTime,
		    timeForLoading);
	}
	return truckArrivalTime;
    }

    private long distanceToTime(int gpsDistance, Truck tr)
    {
	// speed is km/hour, convert to mili-meter/second first
	int truckSpeed = tr.getModel().getCruisingSpeed();
	int speed = truckSpeed * 1000 * 1000 / 3600;
	// gpsDistance
	int miliMeters = LocationUtil.getGpsToMiliMeterDistance(gpsDistance);
	// in mili-seconds
	long time = (miliMeters / speed) * 1000;
	return time;
    }
}
